/*
============================================================================
 Name        : Connection.cpp
 Author      : 
 Version     :
 Copyright   : Your copyright notice
 Description : CConnection implementation
============================================================================
*/

#include "Connection.h"
#include "PluginUtility.h"

#include <Uri16.h>				//TUriParser
#include <stdlib.h>				//atoi

#define BUFFER_SIZE 8192

_LIT8(KConnectionDefaultHeaders, "User-Agent: VersificatorEngine/1.0\r\nAccept: */*\r\n");
_LIT8(KConnectionCOkHeader, "200 OK");
_LIT8(KConnectionContentLengthHeader, "Content-Type:");

CConnection::CConnection()
: CActive(EPriorityStandard), iState(ENotConnected), iUrl(NULL), iRequest(NULL), iResponse(NULL),
iStreamingBuffer(NULL), iStreamingBufferPtr(NULL, 0, 0), iStreamingRequest(EFalse)
{
}

EXPORT_C void CConnection::Destroy()
{
	CConnection* instance = static_cast<CConnection*>(Dll::Tls());
	if (instance)
	{
		delete instance;
		Dll::SetTls(NULL); 
	}
}

CConnection::~CConnection()
{
	Cancel();
	CloseAndClean();

	//Close socket server
	iSocketServ.Close();
}

EXPORT_C CConnection* CConnection::Static()
{
	//Implementation of singleton class
	//See also: http://www.forum.nokia.com/info/sw.nokia.com/id/8b1c70d3-6573-497d-8c6b-08b0e6b3c057/Implementing_a_Singleton_Class_in_Symbian_OS_v1_1_en.pdf.html
	CConnection* instance;
	if (Dll::Tls() == NULL)
	{
		instance = new (ELeave) CConnection();
		CleanupStack::PushL(instance);
		instance->ConstructL();
		CleanupStack::Pop(instance);
		TInt err = Dll::SetTls(static_cast<TAny*>(instance));
		if (err == KErrNone)
		{
			return instance;
		}

		delete instance;
		User::Leave(err);
		return NULL;
	}

	//CConnection has been instantiated once already
	instance = static_cast<CConnection*>(Dll::Tls());
	return instance;
}

void CConnection::ConstructL()
{
	//Create response buffer
	iStreamingBuffer = new (ELeave) TUint8[BUFFER_SIZE];
	iStreamingBufferPtr.Set(iStreamingBuffer, 0, BUFFER_SIZE);

	//Add to scheduler
	CActiveScheduler::Add(this);

	//Connect socket server
	User::LeaveIfError(iSocketServ.Connect());
}

EXPORT_C void CConnection::SetObserver(MConnectionObserver* aObserver)
{
	iObserver = aObserver;
}

EXPORT_C void CConnection::RequestL(const TDesC& aUrl)
{
	//Reset response buffer
	DELETE(iResponse);

	iStreamingRequest = EFalse;
	BuildRequestL(aUrl, KConnectionDefaultHeaders);
}

EXPORT_C void CConnection::StreamingRequestL(const TDesC& aUrl)
{
	iStreamingRequest = ETrue;
	BuildRequestL(aUrl, KConnectionDefaultHeaders);
}

EXPORT_C void CConnection::StreamingRequestL(const TDesC& aUrl, const TDesC8& aAdditionalHeaders)
{
	iStreamingRequest = ETrue;
	BuildRequestL(aUrl, aAdditionalHeaders);
}

void CConnection::BuildRequestL(const TDesC& aUrl, const TDesC8& aAdditionalHeaders)
{
	//Set url
	DELETE(iUrl)
	iUrl = aUrl.AllocL();

	//Construct HTTP: GET command
	TBuf8<512> request;

	//Retrieve path
	TBuf8<256> path;

	//Omit http:// chars
	TBuf<256> url;
	url.Copy(aUrl.Right(aUrl.Length() - 7));

	TInt pos = url.Locate('/');
	if (pos == KErrNotFound)
	{
		//no path
		path.Copy(_L8("/"));
	}
	else
	{
		//path style: '/stream/...'
		url.Delete(0, pos);
		path.Copy(url);
	}

	//Extract host
	TUriParser urlParser;
	urlParser.Parse(aUrl);
	const TDesC& host = urlParser.Extract(EUriHost);

	request.Copy(_L8("GET "));
	request.Append(path);
	request.Append(_L8(" HTTP/1.0\r\n"));
	request.Append(_L8("Host: "));
	request.Append(host);
	request.Append(_L8("\r\n"));

	if (aAdditionalHeaders.Length() > 0)
	{
		request.Append(aAdditionalHeaders);
	}

	//End of request
	request.Append(_L8("\r\n"));

	//Cancel any request, just to be sure
	Cancel();						

	//Set request
	DELETE(iRequest);
	iRequest = request.AllocL();

	//Resolve name
	ResolveL();
}

void CConnection::ResolveL()
{
	TUriParser url;
	url.Parse(iUrl->Des());

	const TDesC& port = url.Extract(EUriPort);

	TBuf8<5> sPort;
	sPort.Copy(port);

	iPort = (unsigned)atoi((const char *)sPort.PtrZ());
	if (iPort == 0) iPort = 80;

	if (url.Extract(EUriScheme).Compare(_L("http")) != 0)
	{
		//Invalid URL - didn't start with "HTTP://"
		User::Leave(KErrArgument);
	}

	iServerName.Copy(url.Extract(EUriHost));

	//Open resolver socket
	User::LeaveIfError(iHostResolver.Open(iSocketServ, KAfInet, KProtocolInetTcp));

	//Attempt to resolve name
	iState = EResolving;
	SetActive();

	iHostResolver.GetByName(iServerName, iHostAddress, iStatus); 
}

void CConnection::ConnectL()
{
	// Recover server's IP address
	iAddress = iHostAddress().iAddr;
	iAddress.SetPort(iPort);

	// Attempt to open the socket
	User::LeaveIfError(iSocket.Open(iSocketServ, KAfInet, KSockStream, KProtocolInetTcp));
	
	iState = EConnecting;
	iSocket.Connect(iAddress, iStatus);
}

void CConnection::SendRequestL()
{
	//Set sending state
	iState = ESending;
	iSocket.Write(*iRequest, iStatus);
}

void CConnection::ReceiveL()
{
	iStreamingBufferPtr.FillZ(BUFFER_SIZE);
	iStreamingBufferPtr.SetLength(0);
	iState = EReceiving;

	//Read data block
	iSocket.Read(iStreamingBufferPtr, iStatus);
}

void CConnection::NotifyReceivedDataL()
{
	if (iStreamingBufferPtr.Length() == 0 && iStatus == KErrEof)
	{
		//Read has finished. Let's search for last chunk
		SearchForLastChunk();
	}
	
	if (iStreamingBufferPtr.Length() > 0)
	{
		//Manage chunk response
		iState = ENotifyingReceived;
		if (iStreamingRequest)
		{
			//Streaming. Notify
			iObserver->StreamingRequestedL(iStreamingBufferPtr);
		}
		else
		{
			//Add to buffer
			if (!iResponse) iResponse = iStreamingBufferPtr.AllocL();
			else
			{
				iResponse = iResponse->ReAllocL(iResponse->Length() + iStreamingBufferPtr.Length());
				iResponse->Des().Append(iStreamingBufferPtr);
			}			
		}
	}

	if (iStatus == KErrEof)
	{
		//Finish of request
		iState = EFinished;
	}

	//Complete request
	SetRequestStatus(KErrNone);
}

void CConnection::Pause()
{
	iState = EPaused;
}

void CConnection::Resume()
{
	iState = ENotifyingReceived;
	if (iStatus == KRequestPending) SetRequestStatus(KErrNone);
}

void CConnection::SetRequestStatus(TInt aStatus)
{
	TRequestStatus* tempStatus = &iStatus;
	User::RequestComplete(tempStatus, aStatus);
}

void CConnection::FinishedL()
{
	//Stop process
	Cancel();

	//Notify that response has finished
	if (iStreamingRequest)
	{
		//Streaming
		iObserver->StreamingEndedL();
	}
	else
	{
		//Http request
		ManageHttpResponseL();
	}
}

void CConnection::DoCancel()
{
	//Cancel socket
	iHostResolver.Cancel();
	iHostResolver.Close();

	iSocket.CancelAll();
	iSocket.Close();

	iState = ENotConnected;
}

void CConnection::CloseAndClean()
{
	//Delete objects
	DELETE(iUrl);
	DELETE(iRequest);
	DELETE(iStreamingBuffer);
	DELETE(iResponse);

	//Close all
	iHostResolver.Close();
	iSocket.Close();
}

void CConnection::RunL()
{
	switch (iState)
	{
	case ENotConnected:
		//do nothing
		break;

	case EResolving:
		//We have just resolved url. Trying to connect
		if (iStatus == KErrNone)
		{
			ConnectL();
		}
		else
		{
			iState = EFailed;
		}
		break;


	case EConnecting:
		//We have just connected. Trying to send request
		if (iStatus == KErrNone)
		{
			SendRequestL();
		}
		else
		{
			iState = EFailed;
		}
		break;

	case ESending:
		//We have just sent request. Trying to receive data
	case ENotifyingReceived:
		//Data has been copied to buffer. Trying to receive more data
		
		if (iStatus == KErrNone)
		{
			ReceiveL();
		}
		else
		{
			iState = EFailed;
		}
		break;

	case EReceiving:
		//Check if we have received any data
		//We have received some data
		if (iStatus == KErrNone || iStatus == KErrEof)
		{
			NotifyReceivedDataL();
		}
		else
		{
			iState = EFailed;
		}
		break;

	case EFinished:
		//All data has been received, so it finishes.
		FinishedL();
		return;

		break;
	}

	if (iState == EFailed)
	{
		//Process failed
		RunError(iStatus.Int());
	}
	else
	{
		//Keep active
		SetActive();
	}
}

TInt CConnection::RunError(TInt aError)
{
	Cancel();
	iObserver->ResponseErrorL(aError);
	iState = EFailed;
}

void CConnection::ManageHttpResponseL()
{
	//Check first line of response
	TInt pos = iResponse->Find(_L8("\r\n"));
	if (pos == KErrNotFound)
	{
		//Error
		iObserver->ResponseErrorL(KErrArgument);
		return;
	}

	TPtrC8 firstLinePtr(iResponse->Ptr(), pos + 1);
	if (firstLinePtr.FindC(KConnectionCOkHeader) == KErrNotFound)
	{
		//Not 200 OK response
		iObserver->ResponseErrorL(KErrArgument);
		return;
	}

	//Delete headers
	TInt firstPos = iResponse->Find(_L8("\r\n\r\n"));
	if (firstPos == KErrNotFound) User::Leave(KErrArgument);

	TInt offset = firstPos + 4;

	//Set pointer to buffer without headers
	iResponse->Des().Copy(iResponse->Ptr() + offset, iResponse->Length() - offset);

	//Http request
	iObserver->ResponseRequestedL(*iResponse);
}

void CConnection::SearchForLastChunk()
{
	TInt i = BUFFER_SIZE;
	TUint8 c;
	iStreamingBufferPtr.SetLength(BUFFER_SIZE);

	while (!iStreamingBufferPtr[--i]);
	iStreamingBufferPtr.SetLength(i + 1);
}